package jps.datastore;

import java.util.Collection;

import edu.pjwstk.jps.datastore.IComplexObject;
import edu.pjwstk.jps.datastore.IOID;
import edu.pjwstk.jps.datastore.ISBAObject;
import edu.pjwstk.jps.datastore.ISBAStore;
import edu.pjwstk.jps.datastore.ISimpleObject;
import edu.pjwstk.jps.datastore.IStringObject;

import java.io.*;
import java.util.*;
import java.lang.reflect.*;

import org.jdom2.*;
import org.jdom2.input.*;

enum SBAType {
	BOOLEAN, INTEGER, DOUBLE, STRING, COMPLEX
}

public class SBAStore implements ISBAStore {
	private Integer nextId = 0;
	private IOID entryOID;
	private Map<IOID, ISBAObject> SBAObjects;
	private String toString = "";

	public SBAStore() {
		SBAObjects = new HashMap<IOID, ISBAObject>();
		IComplexObject object = new ComplexObject(generateUniqueOID(), "entry",
				new ArrayList<IOID>());
		entryOID = object.getOID();
		addSBAObject(object, null);
	}

	@Override
	public ISBAObject retrieve(IOID oid) {
		return SBAObjects.get(oid);
	}

	@Override
	public IOID getEntryOID() {
		return entryOID;
	}

	public String getString() {
		return toString;
	}

	@Override
	public IOID generateUniqueOID() {
		OID oid = new OID(nextId);
		nextId++;
		return oid;
	}

	private void addSBAObject(ISBAObject object, IComplexObject parent) {
		if (parent != null)
			parent.getChildOIDs().add(object.getOID());

		SBAObjects.put(object.getOID(), object);
	}

	@SuppressWarnings("unchecked")
	public SBAObject Create(SBAType type, String name, Object value,
			IOID parentOID) {
		SBAObject obj = null;
		switch (type) {
		case BOOLEAN:
			obj = new BooleanObject(generateUniqueOID(), name, (Boolean) value);
			addSBAObject(obj, (IComplexObject) retrieve(parentOID));
			break;
		case INTEGER:
			obj = new IntegerObject(generateUniqueOID(), name, (Integer) value);
			addSBAObject(obj, (IComplexObject) retrieve(parentOID));
			break;
		case DOUBLE:
			obj = new DoubleObject(generateUniqueOID(), name, (Double) value);
			addSBAObject(obj, (IComplexObject) retrieve(parentOID));
			break;
		case STRING:
			obj = new StringObject(generateUniqueOID(), name, (String) value);
			addSBAObject(obj, (IComplexObject) retrieve(parentOID));
			break;
		case COMPLEX:
			obj = new ComplexObject(generateUniqueOID(), name,
					((ArrayList<IOID>) value));
			addSBAObject(obj, (IComplexObject) retrieve(parentOID));
			break;
		default:
			System.out.println("Nie obslugiwany typ.\n");
			break;
		}
		return obj;
	}

	// obrabianie XMLa przy pomocy jdom'a

	@Override
	public void loadXML(String filePath) {
		try {
			SAXBuilder saxBuilder = new SAXBuilder();
			InputStream inputStream = new FileInputStream(filePath);
			Document document = saxBuilder.build(inputStream);

			Element element = document.getRootElement();

			IComplexObject parent = (IComplexObject) retrieve(getEntryOID());

			if (!element.getChildren().isEmpty())
				parseXML(element, parent);

		} catch (Exception ex) {
			System.out.println(ex.getMessage());
		}
	}

	private void parseXML(Element childNodes, IComplexObject parent) {
		for (Element child : childNodes.getChildren()) {
			loadNode(child, parent);
		}
	}

	public void loadNode(Element node, IComplexObject parent) {
		if (node.getChildren().isEmpty()) {
			@SuppressWarnings("resource")
			Scanner scanner = new Scanner(node.getText());

			if (scanner.hasNextBoolean()) {
				Create(SBAType.BOOLEAN, node.getName(), scanner.nextBoolean(),
						parent.getOID());
			} else if (scanner.hasNextInt()) {
				Create(SBAType.INTEGER, node.getName(), scanner.nextInt(),
						parent.getOID());
			} else if (scanner.hasNextDouble()) {
				Create(SBAType.DOUBLE, node.getName(), scanner.nextDouble(),
						parent.getOID());
			} else {
				Create(SBAType.STRING, node.getName(), node.getText(),
						parent.getOID());
			}
		} else {
			SBAObject object = Create(SBAType.COMPLEX, node.getName(),
					new ArrayList<IOID>(), parent.getOID());
			parent = (IComplexObject) retrieve(object.getOID());

			for (Element child : node.getChildren()) {
				loadNode(child, parent);
			}
		}
	}

	// koniec obrabiania XML'a

	// dodawanie obiektow javowych
	@Override
	public void addJavaObject(Object object, String objectName) {
		if (object == null)
			return;

		if (object instanceof Boolean) {
			Create(SBAType.BOOLEAN, objectName, (Boolean) object, getEntryOID());
		} else if (object instanceof Double) {
			Create(SBAType.DOUBLE, objectName, (Double) object, getEntryOID());
		} else if (object instanceof Integer) {
			Create(SBAType.INTEGER, objectName, (Integer) object, getEntryOID());
		} else if (object instanceof String) {
			Create(SBAType.STRING, objectName, (String) object, getEntryOID());
		} else {
			for (Field field : object.getClass().getFields()) {
				try {
					addJavaObject(field.get(object), field.getName());
				} catch (Exception ex) {
					System.out.println(ex.getMessage());
				}
			}
		}
	}

	@SuppressWarnings("rawtypes")
	@Override
	public void addJavaCollection(Collection collection, String collectionName) {
		for (Object object : collection) {
			addJavaObject(object, collectionName);
		}
	}

	// koniec dodawania obiektow javowych

	// wyswietlanie obiektow w odpowiedniej formie (tak jak na cwiczeniach)

	public void PrintObjects(ISBAObject object) {
		if (object instanceof ISimpleObject)
			if (object instanceof IStringObject)
				toString += "<" + ((OID) object.getOID()).GetValue() + ", "
						+ ((ISimpleObject<?>) object).getName() + ", \""
						+ ((ISimpleObject<?>) object).getValue().toString()
						+ "\">\n";
			else
				toString += "<" + ((OID) object.getOID()).GetValue() + ", "
						+ ((ISimpleObject<?>) object).getName() + ", "
						+ ((ISimpleObject<?>) object).getValue().toString()
						+ ">\n";
		else {
			if (((IComplexObject) object).getName() == "entry")
				toString = "";

			List<IOID> childOIDs = ((IComplexObject) object).getChildOIDs();

			toString += "<"
					+ ((OID) ((IComplexObject) object).getOID()).GetValue()
					+ ", " + ((IComplexObject) object).getName() + ", {";

			for (int i = 0; i < childOIDs.size(); i++) {
				toString += ((OID) childOIDs.get(i)).GetValue() + ", ";
			}

			toString = toString.substring(0, toString.length() - 2);

			toString += "}>\n";

			for (int i = 0; i < childOIDs.size(); i++) {
				PrintObjects(retrieve(childOIDs.get(i)));
			}
		}
	}
}
